//--------------------------------------------------------------------------------
// This file is part of the Windows Workflow Foundation Sample Code
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// This source code is intended only as a supplement to Microsoft
// Development Tools and/or on-line documentation.  See these other
// materials for detailed information regarding Microsoft code samples.
// 
// THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//--------------------------------------------------------------------------------

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing.Design;
using System.Windows.Forms.Design;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Diagnostics;
using System.Reflection;
using System.Globalization;

namespace Microsoft.Samples.Workflow.UI.Design
{
    [ActivityDesignerTheme(typeof(NavigatorDesignerTheme))]
    class NavigatorDesigner : FreeformActivityDesigner, IDesignerGlyphProvider
    {
        const int DefaultChildLayoutDistance = 50;
        const int DefaultMaxWidth = 340;
        static readonly Size MinSize = new Size(100, 80);
        static readonly Size Separator = new Size(30, 30);

        bool updatePropertyOnConnnectorEvents = true;

        public override Size MinimumSize
        {
            get
            {
                //Now go thru all the activity designers and consider the bottom coordinate + selection size + page separator as the min size
                Size minimumSize = DesignerTheme.Size;

                //If the designer is root designer and not inlined then we should occupy the area of workflow view
                if (IsRootDesigner && InvokingDesigner == null)
                {
                    WorkflowView workflowView = ParentView;
                    minimumSize.Width = Math.Max(minimumSize.Width, workflowView.ViewPortSize.Width - 2 * Separator.Width);
                    minimumSize.Height = Math.Max(minimumSize.Height, workflowView.ViewPortSize.Height - 2 * Separator.Height);
                }

                if (AutoSize)
                {
                    Rectangle childRectangle = GetEnclosingRectangle();
                    if (!childRectangle.IsEmpty)
                    {
                        minimumSize.Width = Math.Max(minimumSize.Width, childRectangle.Width);
                        minimumSize.Height = Math.Max(minimumSize.Height, childRectangle.Height);
                    }
                }

                return minimumSize;
            }
        }

        public override bool CanInsertActivities(HitTestInfo insertLocation, ReadOnlyCollection<Activity> activitiesToInsert)
        {
            if (activitiesToInsert == null)
            {
                throw new ArgumentNullException("activitiesToInsert");
            }

            foreach (Activity activity in activitiesToInsert)
            {
                if (Helpers.IsOOBEventTypeActivity(activity.GetType()) ||
                    Helpers.IsOOBStateMachineActivity(activity.GetType()) ||
                    Helpers.IsOOBEventTypeActivity(activity.GetType()))
                {
                    return false;
                }
            }

            return base.CanInsertActivities(insertLocation, activitiesToInsert);
        }

        public override bool CanBeParentedTo(CompositeActivityDesigner parentActivityDesigner)
        {
            if (parentActivityDesigner == null)
            {
                throw new ArgumentNullException("parentActivityDesigner");
            }

            CompositeActivity parentActivity = parentActivityDesigner.Activity as CompositeActivity;
            if (parentActivity == null)
            {
                return false;
            }

            if (!(parentActivity is Navigator || parentActivity is InteractionGroupActivity))
            {
                return false;
            }

            return base.CanBeParentedTo(parentActivityDesigner);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.Activity != null && this.Activity.Site != null)
                {
                    IDesignerGlyphProviderService dgps = this.Activity.Site.GetService(typeof(IDesignerGlyphProviderService)) as IDesignerGlyphProviderService;
                    if (dgps != null)
                    {
                        dgps.RemoveGlyphProvider(this);
                    }
                }
            }

            base.Dispose(disposing);
        }

        protected override void Initialize(Activity activity)
        {
            if (activity == null)
            {
                throw new ArgumentNullException("activity");
            }

            base.Initialize(activity);

            ((Navigator)activity).Activities.ListChanged += OnChildActivitiesListChanged;
            IComponentChangeService componentChangeService = this.GetService(typeof(IComponentChangeService)) as IComponentChangeService;
            if (componentChangeService != null)
            {
                componentChangeService.ComponentChanged += OnComponentChanged;
            }

            if (activity.Site != null)
            {
                IDesignerGlyphProviderService dgps = activity.Site.GetService(typeof(IDesignerGlyphProviderService)) as IDesignerGlyphProviderService;
                if (dgps != null)
                {
                    dgps.AddGlyphProvider(this);
                }
            }

            // Add the TransitionsPropertyExtender for the "Transitions" properties.
            IExtenderListService extenderListService = (IExtenderListService)GetService(typeof(IExtenderListService));
            if (extenderListService != null)
            {
                bool foundNavigationExtender = false;
                foreach (IExtenderProvider extenderProvider in extenderListService.GetExtenderProviders())
                {
                    if (extenderProvider.GetType() == typeof(TransitionsPropertyExtender))
                    {
                        foundNavigationExtender = true;
                    }
                }
                IExtenderProviderService extenderProviderService = (IExtenderProviderService)GetService(typeof(IExtenderProviderService));
                if (extenderProviderService != null)
                {
                    if (!foundNavigationExtender)
                    {
                        extenderProviderService.AddExtenderProvider(new TransitionsPropertyExtender());
                    }
                }
            }

            if (!IsRootDesigner)
            {
                this.AutoSizeMode = AutoSizeMode.GrowAndShrink;
            }
        }

        protected override void OnContainedDesignersConnected(ConnectionPoint source, ConnectionPoint target)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }

            base.OnContainedDesignersConnected(source, target);

            Activity fromActivity = source.AssociatedDesigner.Activity;
            Activity toActivity = target.AssociatedDesigner.Activity;

            TransitionCollection transitions = Navigator.GetTransitions(fromActivity) as TransitionCollection;
            if (!transitions.Contains(toActivity.QualifiedName))
            {
                Transition transition = new Transition(toActivity.QualifiedName);
                transition.Activity = fromActivity;
                transitions.Add(transition);
            }
        }

        protected override void OnConnectorRemoved(ConnectorEventArgs e)
        {
            if (e == null)
            {
                throw new ArgumentNullException("e");
            }

            base.OnConnectorRemoved(e);

            // Only update the properties if the Remove is invoked from the designer.
            if (this.updatePropertyOnConnnectorEvents)
            {
                TransitionCollection transitions = Navigator.GetTransitions(e.Connector.Source.AssociatedDesigner.Activity) as TransitionCollection;
                string targetName = e.Connector.Target.AssociatedDesigner.Activity.QualifiedName;
                if (transitions.Contains(targetName))
                {
                    Transition transition = transitions[targetName];
                    transitions.Remove(transition);
                }
            }
        }

        protected override bool CanConnectContainedDesigners(ConnectionPoint source, ConnectionPoint target)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (target == null)
            {
                throw new ArgumentNullException("target");
            }

            Activity fromActivity = source.AssociatedDesigner.Activity;
            Activity toActivity = target.AssociatedDesigner.Activity;

            // See if already connected.
            foreach (Connector connector in this.Connectors)
            {
                if (connector.Source.AssociatedDesigner == source.AssociatedDesigner &&
                    connector.Target.AssociatedDesigner == target.AssociatedDesigner)
                {
                    return false;
                }
            }

            return base.CanConnectContainedDesigners(source, target);

        }

        protected override void OnLayoutPosition(ActivityDesignerLayoutEventArgs e)
        {
            if (e == null)
            {
                throw new ArgumentNullException("e");
            }

            base.OnLayoutPosition(e);

            RepositionChildren();

            if (IsRootDesigner && InvokingDesigner == null)
            {
                RecalculateRootDesignerSize();
            }
        }

        protected override void LoadViewState(System.IO.BinaryReader reader)
        {
            if (reader == null)
            {
                throw new ArgumentNullException("reader");
            }

            base.LoadViewState(reader);

            if (IsRootDesigner)
            {
                ConfigureConnectors();
            }

            if (this.Activity.Site != null)
            {
                IDesignerGlyphProviderService dgps = this.Activity.Site.GetService(typeof(IDesignerGlyphProviderService)) as IDesignerGlyphProviderService;
                if (dgps != null)
                {
                    dgps.AddGlyphProvider(this);
                }
            }
        }

        internal void RefreshLayout()
        {
            this.PerformLayout();
        }

        internal void AddConnector(Activity sourceActivity, string targetName)
        {
            ActivityDesigner sourceDesigner = NavigatorDesigner.GetDesigner(sourceActivity);
            ReadOnlyCollection<ConnectionPoint> sourceColl = sourceDesigner.GetConnectionPoints(DesignerEdges.Right);
            if (sourceColl == null || sourceColl.Count == 0)
            {
                throw new InvalidOperationException(SR.GetString(SR.Error_CantGetConnectionPoints, sourceActivity.QualifiedName));
            }

            Activity targetActivity = this.Activity.GetActivityByName(targetName, true);
            if (targetActivity == null)
            {
                throw new InvalidOperationException(SR.GetString(SR.Error_CantFindActivity, targetName, this.Activity.QualifiedName));
            }

            ActivityDesigner targetDesigner = NavigatorDesigner.GetDesigner(targetActivity);
            ReadOnlyCollection<ConnectionPoint> destColl = targetDesigner.GetConnectionPoints(DesignerEdges.Top);
            if (destColl == null || destColl.Count == 0)
            {
                throw new InvalidOperationException(SR.GetString(SR.Error_CantGetConnectionPoints, targetName));
            }

            this.AddConnector(sourceColl[0], destColl[0]);
        }

        internal void RemoveConnector(Activity sourceActivity, string targetName)
        {
            this.updatePropertyOnConnnectorEvents = false;

            if (this.Connectors != null && this.Connectors.Count > 0)
            {
                List<Connector> connectors = new List<Connector>(this.Connectors);
                foreach (Connector connector in connectors)
                {
                    ActivityDesigner sourceDesigner = NavigatorDesigner.GetDesigner(sourceActivity);
                    if (connector.Source.AssociatedDesigner == sourceDesigner)
                    {
                        if (targetName != null)
                        {
                            if (targetName == connector.Target.AssociatedDesigner.Activity.QualifiedName)
                            {
                                this.RemoveConnector(connector);
                                break;
                            }
                        }
                        else
                        {
                            this.RemoveConnector(connector);
                        }
                    }
                }
            }

            this.updatePropertyOnConnnectorEvents = true;
        }

        void ConfigureConnectors()
        {
            Queue<CompositeActivity> activities = new Queue<CompositeActivity>();
            activities.Enqueue(this.Activity as CompositeActivity);

            while (activities.Count > 0)
            {
                CompositeActivity activity = activities.Dequeue();
                if (activity is Navigator)
                {
                    foreach (Activity childActivity in activity.Activities)
                    {
                        TransitionCollection transitions = Navigator.GetTransitions(childActivity) as TransitionCollection;
                        foreach (Transition transition in transitions)
                        {
                            if (!ConnectorExist(childActivity.QualifiedName, transition.Target))
                            {
                                AddConnector(childActivity, transition.Target);
                            }
                        }

                        if (childActivity is CompositeActivity)
                        {
                            activities.Enqueue(childActivity as CompositeActivity);
                        }
                    }
                }
                else
                {
                    foreach (Activity childActivity in activity.Activities)
                    {
                        if (childActivity is CompositeActivity)
                        {
                            activities.Enqueue(childActivity as CompositeActivity);
                        }
                    }
                }
            }
        }

        bool ConnectorExist(string sourceActivityName, string targetActivityName)
        {
            foreach (Connector connector in this.Connectors)
            {
                if (connector.Source.AssociatedDesigner.Activity.QualifiedName == sourceActivityName &&
                    connector.Target.AssociatedDesigner.Activity.QualifiedName == targetActivityName)
                {
                    return true;
                }
            }

            return false;
        }

        void RepositionChildren()
        {
            int maximum = DefaultChildLayoutDistance;
            foreach (ActivityDesigner designer in this.ContainedDesigners)
            {
                if (designer.Location.IsEmpty && IsContainedDesignerVisible(designer))
                {
                    if (designer.Location.IsEmpty)
                    {
                        if (IsRootDesigner)
                        {
                            int x = maximum;
                            int y = this.Location.Y + DefaultChildLayoutDistance;
                            designer.Location = new Point(x, y);
                            maximum = designer.Bounds.Right + DefaultChildLayoutDistance;
                        }
                        else
                        {
                            int x = this.Location.X + DefaultChildLayoutDistance;
                            int y = maximum;
                            designer.Location = new Point(x, y);
                            maximum = designer.Bounds.Bottom + DefaultChildLayoutDistance;
                        }
                    }
                }
            }
        }

        void RecalculateRootDesignerSize()
        {
            Debug.Assert(IsRootDesigner && InvokingDesigner == null);

            // Make sure that the root designer has enough
            // space to show all contained designers
            Size newSize = Size.Empty;
            foreach (ActivityDesigner designer in this.ContainedDesigners)
            {
                if (IsContainedDesignerVisible(designer))
                {
                    Rectangle bounds = designer.Bounds;
                    bounds.Offset(Separator.Width - this.Location.X, Separator.Height - this.Location.Y);
                    newSize.Width = Math.Max(newSize.Width, bounds.Right);
                    newSize.Height = Math.Max(newSize.Height, bounds.Bottom);
                }
            }
            newSize.Width = Math.Max(newSize.Width, this.MinimumSize.Width);
            newSize.Height = Math.Max(newSize.Height, this.MinimumSize.Height);
            this.Size = newSize;
        }

        void OnChildActivitiesListChanged(object sender, ActivityCollectionChangeEventArgs e)
        {
            //set the StartWith property if it isnt already set
            if (e.Action == ActivityCollectionChangeAction.Remove || e.Action == ActivityCollectionChangeAction.Replace)
            {
                if (e.RemovedItems != null && e.RemovedItems.Count > 0)
                {
                    string startWith = ((Navigator)this.Activity).StartWith;
                    if (!string.IsNullOrEmpty(startWith))
                    {
                        foreach (Activity removedChild in e.RemovedItems)
                        {
                            if (startWith == removedChild.QualifiedName)
                            {
                                ((Navigator)this.Activity).StartWith = null;
                            }

                            foreach (Connector connector in this.Connectors)
                            {
                                if (connector.Source.AssociatedDesigner.Activity.QualifiedName == removedChild.QualifiedName ||
                                    connector.Target.AssociatedDesigner.Activity.QualifiedName == removedChild.QualifiedName)
                                {
                                    // This will also remove the NavigateTo property value when showing actual transitions
                                    // and from the Transitions when showing possible transitions.
                                    this.RemoveConnector(connector);
                                }
                            }

                            // Remove from possible transitions lists
                            foreach (Activity childActivity in ((Navigator)this.Activity).Activities)
                            {
                                TransitionCollection transitions = Navigator.GetTransitions(childActivity) as TransitionCollection;
                                if (transitions.Contains(removedChild.QualifiedName))
                                {
                                    transitions.Remove(removedChild.QualifiedName);
                                }
                            }
                        }
                    }
                }
            }
        }

        void OnComponentChanged(object sender, ComponentChangedEventArgs e)
        {
            Activity activity = e.Component as Activity;
            if (activity == null || activity.Parent != this.Activity || e.Member == null || e.Member.Name != "Name")
            {
                return;
            }

            string startWith = ((Navigator)this.Activity).StartWith;
            if (!string.IsNullOrEmpty(startWith) && startWith == e.OldValue as string)
            {
                ((Navigator)this.Activity).StartWith = e.NewValue as string;
            }
            
            foreach (Activity childActivity in ((Navigator)this.Activity).Activities)
            {
                // Change the entry in the possible transitions list.
                TransitionCollection transitions = Navigator.GetTransitions(childActivity) as TransitionCollection;
                if (transitions.Contains((string)e.OldValue))
                {
                    Transition transition = transitions[(string)e.OldValue];
                    transition.Target = (string)e.NewValue;
                }
            }
        }


        Rectangle GetEnclosingRectangle()
        {
            Point leftTop = new Point(int.MaxValue, int.MaxValue), rightBottom = new Point(int.MinValue, int.MinValue);
            foreach (ActivityDesigner activityDesigner in ContainedDesigners)
            {
                if (activityDesigner.IsVisible)
                {
                    leftTop.X = (activityDesigner.Bounds.Left < leftTop.X) ? activityDesigner.Bounds.Left : leftTop.X;
                    leftTop.Y = (activityDesigner.Bounds.Top < leftTop.Y) ? activityDesigner.Bounds.Top : leftTop.Y;
                    rightBottom.X = (rightBottom.X < activityDesigner.Bounds.Right) ? activityDesigner.Bounds.Right : rightBottom.X;
                    rightBottom.Y = (rightBottom.Y < activityDesigner.Bounds.Bottom) ? activityDesigner.Bounds.Bottom : rightBottom.Y;
                }
            }

            //for the invoked workflow dont take connectors into account
            //this causes un-necessary growth of the workflow...
            if (this.InvokingDesigner == null)
            {
                foreach (Connector connector in Connectors)
                {
                    leftTop.X = (connector.Bounds.Left < leftTop.X) ? connector.Bounds.Left : leftTop.X;
                    leftTop.Y = (connector.Bounds.Top < leftTop.Y) ? connector.Bounds.Top : leftTop.Y;
                    rightBottom.X = (rightBottom.X < connector.Bounds.Right) ? connector.Bounds.Right : rightBottom.X;
                    rightBottom.Y = (rightBottom.Y < connector.Bounds.Bottom) ? connector.Bounds.Bottom : rightBottom.Y;
                }
            }

            Rectangle enclosingRectangle = Rectangle.Empty;
            if (leftTop.X != int.MaxValue && rightBottom.X != int.MinValue)
            {
                enclosingRectangle.X = leftTop.X;
                enclosingRectangle.Width = rightBottom.X - leftTop.X;
            }
            if (leftTop.Y != int.MaxValue && rightBottom.Y != int.MinValue)
            {
                enclosingRectangle.Y = leftTop.Y;
                enclosingRectangle.Height = rightBottom.Y - leftTop.Y;
            }

            if (!enclosingRectangle.IsEmpty)
                enclosingRectangle.Inflate(AutoSizeMargin);

            return enclosingRectangle;
        }

        ActivityDesignerGlyphCollection IDesignerGlyphProvider.GetGlyphs(ActivityDesigner activityDesigner)
        {
            Navigator nav = this.Activity as Navigator;
            string startActivity = nav.GetValue(Navigator.StartWithProperty) as string;
            ActivityDesignerGlyphCollection glyphs = new ActivityDesignerGlyphCollection();

            if (!string.IsNullOrEmpty(startActivity))
            {
                Activity c = nav.GetActivityByName(startActivity, true);
                if (c != null && c.QualifiedName == activityDesigner.Activity.QualifiedName)
                {
                    glyphs.Add(new ShadowGlyph());
                }
            }
            return glyphs;
        }

        internal static ActivityDesigner GetDesigner(Activity activity)
        {
            IDesignerHost designerHost = GetDesignerHost(activity);
            ActivityDesigner designer = designerHost.GetDesigner(activity) as ActivityDesigner;
            if (designer == null)
            {
                throw new InvalidOperationException(SR.GetString(SR.Error_CantGetDesigner, activity.Name));
            }

            return designer;
        }

        static IDesignerHost GetDesignerHost(Activity activity)
        {
            if (activity == null)
            {
                throw new ArgumentNullException("activity");
            }
            if (activity.Site == null)
            {
                throw new InvalidOperationException(SR.GetString(SR.Error_ActivityNotSited, activity.Name));
            }

            IDesignerHost designerHost = activity.Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
            if (designerHost == null)
            {
                throw new InvalidOperationException(SR.GetString(SR.General_MissingService, typeof(IDesignerHost).FullName));
            }

            return designerHost;
        }

        class NavigatorDesignerTheme : CompositeDesignerTheme
        {
            public NavigatorDesignerTheme(WorkflowTheme theme)
                : base(theme)
            {
                this.ShowDropShadow = true;
                this.ConnectorStartCap = LineAnchor.None;
                this.ConnectorEndCap = LineAnchor.ArrowAnchor;
                this.ForeColor = Color.FromArgb(0xFF, 0x00, 0x00, 0x00);
                this.BorderColor = Color.FromArgb(0xFF, 0x49, 0x77, 0xB4);
                this.BorderStyle = DashStyle.Solid;
                this.BackColorStart = Color.FromArgb(0x00, 0x00, 0x00, 0x00);
                this.BackColorEnd = Color.FromArgb(0x00, 0x00, 0x00, 0x00);
            }
        }
    }
}